package com.zdft.promotion.common.service.impl;

import com.zdft.promotion.common.service.api.ICacheService;
import com.zdft.promotion.common.service.api.IGetter;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;

/**
 * Created by Administrator on 2019/2/28.
 */
@Service
public class CacheServiceImpl implements ICacheService {
    private static final Logger log  = LoggerFactory.getLogger(ICacheService.class);
    private static final String NULL = "_null";

    @Value("${app.code}") private String appCode;

    @Resource private StringRedisTemplate stringRedisTemplate;

    private ValueOperations<String, String> operations;

    @Override
    public <T> T get(String key) {
        return get(key, null, null, 0);
    }

    @Override
    public <P, T> T get(String key, IGetter<P, T> getter, P argument, int keepTime) {
        key = toInnerKey(key);
        String value = operations.get(key);
        if (StringUtils.isEmpty(value)) {
            log.debug("[Key:{}]none cache hit.", key);
            if (null == getter) {
                return null;
            }

            if (keepTime <= 0) {
                throw new IllegalArgumentException("Must provide keep time!");
            }
            T t = getter.get(argument);
            setNxInner(key, t, keepTime);
            return t;
        }

        log.debug("[Key:{}]hit cache.", key);
        if (NULL.equals(value)) {
            return null;
        }
        try {
            return SerializationUtils.deserialize(Hex.decodeHex(value));
        } catch (Throwable e) {
            log.error("[Key:{}]value from cache maybe not recover to object, delete!", key);
            stringRedisTemplate.delete(key);
            throw new RuntimeException(e);
        }
    }


    @Override
    public void set(String key, Object value, int keepTime) {
        key = toInnerKey(key);

        String v = toInnerValue(value);
        log.debug("[Key:{}]set value to cache, keep time:{}.", key, keepTime);
        operations.set(key, v, keepTime, TimeUnit.SECONDS);
    }

    @Override
    public void setAWeek(String key, Object value, int keepTime) {
        key = toInnerKey(key);

        String v = toInnerValue(value);
        log.debug("[Key:{}]set value to cache, keep time:{}.", key, keepTime);
        operations.set(key, v, keepTime, TimeUnit.DAYS);
    }

    @Override
    public boolean setNx(String key, Object value, int keepTime) {
        key = toInnerKey(key);
        return setNxInner(key, value, keepTime);
    }

    @Override
    public void delete(String key) {
        key = toInnerKey(key);
        log.error("[Key:{}]delete value from cache.", key);
        stringRedisTemplate.delete(key);
    }

    @Override
    public void expire(String key, int keepTime) {
        key = toInnerKey(key);
        log.error("[Key:{}]set new keep time:{}.", key, keepTime);
        stringRedisTemplate.expire(key, keepTime, TimeUnit.SECONDS);
    }

    @PostConstruct
    public void init() {
        operations = stringRedisTemplate.opsForValue();
    }

    private String toInnerKey(String key) {
        if (StringUtils.isEmpty(key)) {
            throw new IllegalArgumentException("Must provide key!");
        }
        return appCode + '_' + key;
    }

    private String toInnerValue(Object value) {
        if (null == value) {
            return NULL;
        }
        return Hex.encodeHexString(SerializationUtils.serialize((Serializable) value));
    }

    private boolean setNxInner(String innerKey, Object value, int keepTime) {
        String v = toInnerValue(value);
        log.debug("[Key:{}]set value to cache if not existed, keep time:{}.", innerKey, keepTime);
        boolean result = operations.setIfAbsent(innerKey, v, keepTime, TimeUnit.SECONDS);
        log.debug("[Key:{}]set result:{}.", innerKey, result);
        return result;
    }
}
